<?php 
/**
 * This file is part of DomAr.
 * 
 * DomAr is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * DomAr is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with DomAr.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */

/**
* @package DomAr
* @subpackage Relation
*/
class DomArManyToMany extends DomArManyToOne implements IDomArPropertyInsertListener, IDomArPropertyUpdateListener{
	
	/**
	* @param String
	*/
	protected $relationsTableName = NULL;
	
	/**
	* @param String
	*/
	protected $relationDatabase = NULL;
	
	
	/**
	* @param String $className
	* @param String $name
	* @param String $childClassName
	* @param Boolean $composition
	* @param Boolean $actAsParent
	* @param String $inverse
	* @param Boolean $inverseToMany 
	*/
	public function __construct( $className, $name, $childClassName, $composition = false, $actAsParent = true, $inverse = NULL, $itemValidator = NULL ) {
		parent::__construct( $className, $name, $childClassName, $composition, $actAsParent, $inverse, $itemValidator  );

		$parentClassName = ($this->actAsParent === true ? $this->className : $this->childClassName);
		$this->relationDatabase = call_user_func(array( $parentClassName ,'getDatabase'));		
		$this->relationsTableName = call_user_func(array( $parentClassName ,'getTableName')).'_'.($this->actAsParent === true ? $this->name : $this->inverse).'_bindings';
		
		if( strlen($this->relationsTableName) > 64 )
			throw new RuntimeException('Relation table name is too long! (over 64 characters!). Try to make shorter class names for class '.$className.' or '.$childClassName.' or make property name '.($this->actAsParent === true ? $this->name : $this->inverse).' shorter.');

	}
	
	
	public function insert( DomArObject $object, &$objectsSet, DomArValueSet $set ) {
		if( is_object($objectsSet) === true ) {
			if( $object->id == 0 ) {
				$object->setChanged( $this->name, true );
				return;
			}
			foreach( $objectsSet->getNewObjects() as $object ) {
				if( $object->id == 0) {
					$object->setChanged( $this->name, true );
					return;
				}
			}
		}
	}
	
	/**
	* @param DomArObject $object
	* @param DomArSet $set
	* @return String
	*/
	public function update( DomArObject $object, &$objectsSet, DomArValueSet $set ) {
	
		if( is_object($objectsSet) === true ) {	
			
			foreach( $objectsSet->getNewObjects() as $object ) {
				
				$id = $object->getId();
				$parentId = $objectsSet->getParent()->getId();			
				
				if( $this->actAsParent === true ) {
					if( $parentId > 0 && $id > 0 ) {
						try {
							DomSql::query( 'INSERT INTO '.$this->getRelationDbTable().' (parentId,childId) VALUES ('.($this->actAsParent === true ? $parentId.','.$id : $id.','.$parentId).')' );
						} catch( RuntimeException $e ) {
							if( $e->getCode() != 1062 )
								throw $e;
						}
					} else {
						$object->setChanged( $this->name, true );
					}
				}			
			}
		}
	}
	
	/**
	* @param DomArTable $table Parent table.
	*/
	public function alterTable( DomArTable $table ) {
		
		DomArTable::get( $this->childClassName )->update();		
		
		$relationsTable = new DomSqlTable( $this->relationsTableName );
		$relationsTable->setDatabase( $this->relationDatabase );
		$relationsTable->create();
		$relationsTable->addColumn('parentId', 'bigint', false);
		$relationsTable->addColumn('childId', 'bigint', false);
				
		$relationsTable->addIndex('parentId_childId_unique', array('parentId','childId'), 'unique');
		
		$relationsTable->dropColumn('id');		
		
	}
	
	/**
	* Removes parent from children if inversed or deletes children if composition
	* @param DomArObject $object
	* @param DomArObject $propertyValue
	*/
	public function beforeDelete( DomArObject $object, &$set ) {
		
		foreach( $set->query()->commit() as $childObject ) {

			$object->{$this->name}->unload( $set->getParent() );
			
			if( $this->composition === true )
				$childObject->delete();
	
		}
		
		if( $this->actAsParent === true )
			DomSql::query( 'DELETE FROM '.$this->getRelationDbTable().' WHERE parentId = '.$set->getParent()->getId().'');
		else 
			DomSql::query( 'DELETE FROM '.$this->getRelationDbTable().' WHERE childId = '.$set->getParent()->getId().'');
				
		$set->flushNewObjects();
		$set->flushLoadedObjects();
	}
	
	
	
	
	
	
	
	
	
	/**
	* @return String
	*/
	public function getRelationsTableName() {
		return $this->relationsTableName;
	}
	
	/**
	* @return String
	*/
	public function getRelationDbTable() {
		return ($this->relationDatabase ? '`'.$this->relationDatabase.'`.' : NULL ) . '`'.$this->relationsTableName.'`';
	}
	
	/**
	* @param DomArSet $set
	* @param DomArObject $object
	* @param Boolean $rec If validation is checked for child object also.
	* @return Boolean
	*/
	public function set_validate( DomArObject $parent, DomArObject $object, array &$customParams = array(), $validateParentForChild = true ) {

		$validated = $object instanceof $this->childClassName  && call_user_func(array(get_class($object), 'getDbTable')) == call_user_func(array($this->childClassName, 'getDbTable')) ? 0 : 1;
		if( $validated !== 0) {
			return $validated;
		}
		
		if( $this->itemValidator[0] != '' ) {
			$result = (int)$parent->{$this->itemValidator[0]}( $object, $customParams );
			if( $result !== 0 ) {
				return $result;
			}
		}
		
		if( $this->inverse && $validateParentForChild === true) {
			return $object->getProperty($this->inverse)->set_validate( $object, $parent, $customParams, false );
		}
		
		return 0;
	}
	
	/**
	* @param DomArSet $set
	* @param DomArObject $object
	*/
	public function set_add( DomArSet $set, DomArObject $object ) {
		
		// test if already exists in set
		if( $object->id > 0 && $set->getParent()->id > 0 ) {
			$existsAlready = (bool)DomSql::selectOneQuery(
					'SELECT IF(t.childId>0,1,0) as r FROM '.$this->getRelationDbTable().' as t WHERE 
					'.(
						$this->actAsParent === true ?
						't.childId = '.(int)$object->id.' AND t.parentId='.(int)$set->getParent()->id.'' :
						't.parentId = '.(int)$object->id.' AND t.childId='.(int)$set->getParent()->id.''
					).'
					LIMIT 1','r'
				);
			if( $existsAlready === true ) {
				$result = $set->addLoadedObject( $object );
				return;
			}
		} else if( $set->getParent()->id > 0 && $set->isChildNew( $object ) ) {
			return;
		}
		// ------
		
		$customParams = array();
		if( ($e = $this->set_validate( $set->getParent(), $object, $customParams, true )) === 0 ) {
			
			// before add listeners
			$this->invokeListener('beforeadd', $set->getParent(), $object );
			// --------
			
			
			if( $this->inverse !== NULL) {
				$object->getProperty($this->inverse)->invokeListener('beforeadd', $object, $set->getParent() );
				$object->{$this->inverse}->addNewObject( $set->getParent() );
				$object->getProperty($this->inverse)->invokeListener('afteradd', $object, $set->getParent() );
			}
			
			$result = $set->addNewObject( $object );
			
			
			// after add listeners
			$this->invokeListener('afteradd', $set->getParent(), $object );
			// --------
			
			 
		} else {
			$set->getParent()->registerFailedValidation( $e, $this->name, get_class($object), $customParams );
			//throw new InvalidArgumentException('Can\'t add object from class <strong>'.get_class($object).'</strong> to set which expects class <strong>'.$this->childClassName.'</strong> and tableName <strong>'.call_user_func(array($this->childClassName, 'getTableName')).'</strong> !');
		}
	
	}
	
	
	/**
	* @param DomArSet $set
	* @param Int $id
	* @return DomArObject
	*/
	public function set_find( DomArSet $set, $id ) {
		$r = $this->set_query( $set )->where(($this->actAsParent === true ? 'r.childId ' : 'r.parentId ').'='.(int)$id )->commit()->rewind();
		return $r->current();
	}
	
	
		
	/**
	* @param DomArSet $set
	* @return Int
	*/
	public function set_count(DomArSet $set ) {
		return (int)DomSql::selectOneQuery( 'SELECT count(*) as n FROM ' . $this->getRelationDbTable().' WHERE '.($this->actAsParent === true ? 'parentId' : 'childId') . ' = ' . $set->getParent()->id , 'n');
	}
	
	/**
	* @param DomArSet $set
	* @param DomArObject $object
	*/
	public function set_remove( DomArSet $set, DomArObject $object ) {
		
		// before remove listeners
		$this->invokeListener('beforeremove', $set->getParent(), $object );
		// --------
		
		$set->unload( $object );
		
		$object->getProperty($this->inverse)->invokeListener('beforeremove', $object, $set->getParent() );
		$object->{$this->inverse}->unload( $set->getParent() );
		$object->getProperty($this->inverse)->invokeListener('afterremove', $object, $set->getParent() );
		
		if( $object->getId() > 0 && $set->getParent()->getId() > 0 ) { 
			
			$object->save();
			
			if( $this->actAsParent === true )
				DomSql::query( 'DELETE FROM '.$this->getRelationDbTable().' WHERE parentId = '.$set->getParent()->getId().' AND childId = '.$object->getId().' LIMIT 1' );
			else
				DomSql::query( 'DELETE FROM '.$this->getRelationDbTable().' WHERE childId = '.$set->getParent()->getId().' AND parentId = '.$object->getId().' LIMIT 1' );
		}
		
		
		// after remove listeners
		$this->invokeListener('afterremove', $set->getParent(), $object );
		// --------
	}
	



	/**
	* Prepares search query for execution
	* @param DomArSet $set
	* @param DomArDataQuery $query
	*/
	protected function prepareQuery( DomArSet $set, DomSqlQuery $query  ){
		$query->from( $this->getRelationDbTable(),'r' );
		$parentId = $set->getParent()->id > 0 ? (int)$set->getParent()->id : 'NULL';
		
		if( $this->actAsParent === true ) {
			return $query->leftjoin( $this->getChildrenDbTable(), 't', 'r.childId = t.id' )->where('r.parentId='.$parentId);
		} else {
			return $query->leftjoin( $this->getChildrenDbTable(), 't', 'r.parentId = t.id' )->where('r.childId='.$parentId);
		}
		
	}
	
	
	
}

?>